/* proxydroid - Global / Individual Proxy App for Android
 * Copyright (C) 2011 Max Lv <max.c.lv@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * 
 *                            ___====-_  _-====___
 *                      _--^^^#####//      \\#####^^^--_
 *                   _-^##########// (    ) \\##########^-_
 *                  -############//  |\^^/|  \\############-
 *                _/############//   (@::@)   \\############\_
 *               /#############((     \\//     ))#############\
 *              -###############\\    (oo)    //###############-
 *             -#################\\  / VV \  //#################-
 *            -###################\\/      \//###################-
 *           _#/|##########/\######(   /\   )######/\##########|\#_
 *           |/ |#/\#/\#/\/  \#/\##\  |  |  /##/\#/  \/\#/\#/\#| \|
 *           `  |/  V  V  `   V  \#\| |  | |/#/  V   '  V  V  \|  '
 *              `   `  `      `   / | |  | | \   '      '  '   '
 *                               (  | |  | |  )
 *                              __\ | |  | | /__
 *                             (vvv(VVV)(VVV)vvv)
 *
 *                              HERE BE DRAGONS
 *
 */

package proxydroid.org.proxydroid;

import java.io.FileOutputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.List;


import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;


import com.flurry.android.FlurryAgent;
import com.wztk.shydemo.proxy.ShellUtils;

import proxydroid.com.btr.proxy.selector.pac.PacProxySelector;
import proxydroid.com.btr.proxy.selector.pac.PacScriptSource;
import proxydroid.com.btr.proxy.selector.pac.Proxy;
import proxydroid.com.btr.proxy.selector.pac.UrlPacScriptSource;
import proxydroid.org.proxydroid.utils.Utils;

public class ProxyDroidService {

    private Notification notification;
    private NotificationManager notificationManager;
    private Intent intent;
    private PendingIntent pendIntent;

    public static final String BASE = "/data/data/org.proxydroid/";
    private static final int MSG_CONNECT_START = 0;
    private static final int MSG_CONNECT_FINISH = 1;
    private static final int MSG_CONNECT_SUCCESS = 2;
    private static final int MSG_CONNECT_FAIL = 3;
    private static final int MSG_CONNECT_PAC_ERROR = 4;
    private static final int MSG_CONNECT_RESOLVE_ERROR = 5;
    final static String CMD_IPTABLES_RETURN = "iptables -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN\n";
    final static String CMD_IPTABLES_REDIRECT_ADD_HTTP = "iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to 8123\n"
            + "iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to 8124\n"
            + "iptables -t nat -A OUTPUT -p tcp --dport 5228 -j REDIRECT --to 8124\n";
    final static String CMD_IPTABLES_DNAT_ADD_HTTP = "iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:8123\n"
            + "iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 127.0.0.1:8124\n"
            + "iptables -t nat -A OUTPUT -p tcp --dport 5228 -j DNAT --to-destination 127.0.0.1:8124\n";
    final static String CMD_IPTABLES_REDIRECT_ADD_HTTP_TUNNEL = "iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to 8123\n"
            + "iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to 8123\n"
            + "iptables -t nat -A OUTPUT -p tcp --dport 5228 -j REDIRECT --to 8123\n";
    final static String CMD_IPTABLES_DNAT_ADD_HTTP_TUNNEL = "iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:8123\n"
            + "iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 127.0.0.1:8123\n"
            + "iptables -t nat -A OUTPUT -p tcp --dport 5228 -j DNAT --to-destination 127.0.0.1:8123\n";
    final static String CMD_IPTABLES_REDIRECT_ADD_SOCKS = "iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to 8123\n";
    final static String CMD_IPTABLES_DNAT_ADD_SOCKS = "iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:8123\n";

    private static final String TAG = "ProxyDroidService";

    private String host;
    private int port;
    private String bypassAddrs = "";
    private String user;
    private String password;
    private String domain;
    private String proxyType = "http";
    private String auth = "false";
    private boolean isAuth = false;
    private boolean isNTLM = false;         //NTLM认证
    private boolean isDNSProxy = false;
    private boolean isPAC = false;

    private DNSProxy dnsServer = null;
    private int dnsPort = 0;

    Process NTLMProcess;

    private SharedPreferences settings = null;

    private boolean hasRedirectSupport = true;              //是否可以转发，默认为是，目前
    private boolean isAutoSetProxy = false;
    private boolean isBypassApps = false;

    private ProxyedApp apps[];

    private static final Class<?>[] mSetForegroundSignature = new Class[]{boolean.class};
    private static final Class<?>[] mStartForegroundSignature = new Class[]{int.class,
            Notification.class};
    private static final Class<?>[] mStopForegroundSignature = new Class[]{boolean.class};

    private Method mSetForeground;
    private Method mStartForeground;
    private Method mStopForeground;

    private Object[] mSetForegroundArgs = new Object[1];
    private Object[] mStartForegroundArgs = new Object[2];
    private Object[] mStopForegroundArgs = new Object[1];

    private Profile profile;

    public void setProfile(Profile profile) {
        this.profile = profile;
    }

    public Profile getProfile() {
        return profile;
    }

    //构造函数
    public ProxyDroidService(Profile profile) {
        this.profile = profile;
    }

    void invokeMethod(Method method, Object[] args) {
        try {
            method.invoke(this, mStartForegroundArgs);
        } catch (InvocationTargetException e) {
            // Should not happen.
            Log.w("ApiDemos", "Unable to invoke method", e);
        } catch (IllegalAccessException e) {
            // Should not happen.
            Log.w("ApiDemos", "Unable to invoke method", e);
        }
    }
    /**
     * This is a wrapper around the new startForeground method, using the older
     * APIs if it is not available.
     */
    void startForegroundCompat(int id, Notification notification) {
        // If we have the new startForeground API, then use it.
        if (mStartForeground != null) {
            mStartForegroundArgs[0] = Integer.valueOf(id);
            mStartForegroundArgs[1] = notification;
            invokeMethod(mStartForeground, mStartForegroundArgs);
            return;
        }

        // Fall back on the old API.
        mSetForegroundArgs[0] = Boolean.TRUE;
        invokeMethod(mSetForeground, mSetForegroundArgs);
        notificationManager.notify(id, notification);
    }

    /**
     * This is a wrapper around the new stopForeground method, using the older
     * APIs if it is not available.
     */
    void stopForegroundCompat(int id) {
        // If we have the new stopForeground API, then use it.
        if (mStopForeground != null) {
            mStopForegroundArgs[0] = Boolean.TRUE;
            try {
                mStopForeground.invoke(this, mStopForegroundArgs);
            } catch (InvocationTargetException e) {
                // Should not happen.
                Log.w("ApiDemos", "Unable to invoke stopForeground", e);
            } catch (IllegalAccessException e) {
                // Should not happen.
                Log.w("ApiDemos", "Unable to invoke stopForeground", e);
            }
            return;
        }

        // Fall back on the old API. Note to cancel BEFORE changing the
        // foreground state, since we could be killed at that point.
        notificationManager.cancel(id);
        mSetForegroundArgs[0] = Boolean.FALSE;
        invokeMethod(mSetForeground, mSetForegroundArgs);
    }

    /**
     * Internal method to request actual PTY terminal once we've finished
     * authentication. If called before authenticated, it will just fail.
     */
    private void enableProxy() {

        String proxyHost = host;
        int proxyPort = port;

        try {
            if (proxyType.equals("https")) {

                // Configure file for Stunnel
                FileOutputStream fs = new FileOutputStream(BASE + "stunnel.conf");
                String conf = "debug = 0\n" + "client = yes\n" + "pid = " + BASE + "stunnel.pid\n"
                        + "[https]\n" + "sslVersion = all\n" + "accept = 127.0.0.1:8126\n"
                        + "connect = " + host + ":" + port + "\n";
                fs.write(conf.getBytes());
                fs.flush();
                fs.close();

                // Start stunnel here
                Utils.runRootCommand(BASE + "stunnel " + BASE + "stunnel.conf");

                // Reset host / port
                proxyHost = "127.0.0.1";
                proxyPort = 8126;
                proxyType = "http";
            }

            if (proxyType.equals("http") && isAuth && isNTLM) {
                Utils.runRootCommand(BASE + "proxy.sh start http 127.0.0.1 8025 false\n" + BASE
                        + "cntlm -P " + BASE + "cntlm.pid -l 8025 -u " + user
                        + (!domain.equals("") ? "@" + domain : "@local") + " -p " + password + " "
                        + proxyHost + ":" + proxyPort + "\n" + BASE
                        + "tproxy -P /data/data/org.proxydroid/tproxy.pid -s 8125 127.0.0.1 8025\n");
            } else {
                final String u = Utils.preserve(user);
                final String p = Utils.preserve(password);

                Utils.runRootCommand(BASE + "proxy.sh start" + " " + proxyType + " " + proxyHost
                        + " " + proxyPort + " " + auth + " \"" + u + "\" \"" + p + "\"");
            }

            StringBuffer cmd = new StringBuffer();

            //DNS代理暂时不做
//            if (isDNSProxy) {
//                dnsServer = new DNSProxy(this, dnsPort);
//                dnsPort = dnsServer.init();
//
//                Thread dnsThread = new Thread(dnsServer);
//                dnsThread.setDaemon(true);
//                dnsThread.start();
//
//                if (hasRedirectSupport)
//                    cmd.append("iptables -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to "
//                            + dnsPort + "\n");
//                else
//                    cmd.append("iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:"
//                            + dnsPort + "\n");
//
//            }

            cmd.append(CMD_IPTABLES_RETURN.replace("0.0.0.0", host));

            if (bypassAddrs != null && !bypassAddrs.equals("")) {
                String[] addrs = Profile.decodeAddrs(bypassAddrs);
                for (String addr : addrs)
                    cmd.append(CMD_IPTABLES_RETURN.replace("0.0.0.0", addr));
            }

            String redirectCmd = CMD_IPTABLES_REDIRECT_ADD_HTTP;
            String dnatCmd = CMD_IPTABLES_DNAT_ADD_HTTP;

            if (proxyType.equals("socks4") || proxyType.equals("socks5")) {
                redirectCmd = CMD_IPTABLES_REDIRECT_ADD_SOCKS;
                dnatCmd = CMD_IPTABLES_DNAT_ADD_SOCKS;
            } else if (proxyType.equals("http-tunnel")) {
                redirectCmd = CMD_IPTABLES_REDIRECT_ADD_HTTP_TUNNEL;
                dnatCmd = CMD_IPTABLES_DNAT_ADD_HTTP_TUNNEL;
            }

            //如果为全局则proxyedApps，为空。isBypassApps为false
            //如果false，则proxyedApps中的app设置为代理，其他不设置为代理
            //如果true，则proxyedApps中的app不设置代理，其他设置为代理
            //对于此项目来说这两个参数没有意义
            if (isBypassApps) {
                // for host specified apps
                if (apps == null || apps.length <= 0)
                    apps = AppManager.getProxyedApps(this, false);

                for (int i = 0; i < apps.length; i++) {
                    if (apps[i] != null && apps[i].isProxyed()) {
                        cmd.append(CMD_IPTABLES_RETURN.replace("-d 0.0.0.0", "").replace("-t nat",
                                "-t nat -m owner --uid-owner " + apps[i].getUid()));
                    }
                }
            }

            if (isAutoSetProxy || isBypassApps) {
                cmd.append(hasRedirectSupport ? redirectCmd : dnatCmd);
            } else {
                // for host specified apps
                if (apps == null || apps.length <= 0)
                    apps = AppManager.getProxyedApps(this, true);

                for (int i = 0; i < apps.length; i++) {
                    if (apps[i] != null && apps[i].isProxyed()) {
                        cmd.append((hasRedirectSupport ? redirectCmd : dnatCmd).replace("-t nat",
                                "-t nat -m owner --uid-owner " + apps[i].getUid()));
                    }
                }
            }
            String rules = cmd.toString();
            if (proxyType.equals("http") && isAuth && isNTLM)
                rules = rules.replace("8123", "8125");
            rules = rules.replace("iptables", Utils.getIptables());
            Utils.runRootCommand(rules);

        } catch (Exception e) {
            Log.e(TAG, "Error setting up port forward during connect", e);
        }

    }

    /**
     * Called when the activity is first created.
     */
    public boolean handleCommand() {
        String cmds = "chmod 777 /data/local/tmp/proxy_test/iptables\n"
                + "chmod 777 /data/local/tmp/proxy_test/redsocks\n"
                + "chmod 777 /data/local/tmp/proxy_test/proxy.sh\n"
                + "chmod 777 /data/local/tmp/proxy_test/cntlm\n"
                + "chmod 777 /data/local/tmp/proxy_test/tproxy\n"
                + "chmod 777 /data/local/tmp/proxy_test/stunnel\n";
        ShellUtils.execCommand(cmds, true);
        enableProxy();
        return true;
    }

    private void stopProxy() {
        final StringBuilder sb = new StringBuilder();
        sb.append(Utils.getIptables() + " -t nat -F OUTPUT\n");
        if (proxyType.equals("https")) {
            sb.append("kill -9 `cat /data/local/tmp/proxy_test/stunnel.pid`\n");
        }
        if (isAuth && isNTLM) {
            sb.append("kill -9 `cat /data/local/tmp/proxy_test/cntlm.pid`\n"
                    + "kill -9 `cat /data/local/tmp/proxy_test/tproxy.pid`\n");
        }
        sb.append(BASE + "proxy.sh stop\n");
    }

    //入口
    public void startProxy(Intent intent, int startId) {
        if (intent == null || intent.getExtras() == null) {
            return;
        }
        Log.d(TAG, "Service Start");
        host = this.profile.getHost();
        bypassAddrs = this.profile.getBypassAddrs();
        proxyType = this.profile.getProxyType();
        port = this.profile.getPort();
        isAutoSetProxy = this.profile.isAutoSetProxy();
        isBypassApps = this.profile.isBypassApps();
        isAuth = this.profile.isAuth();
        isNTLM = this.profile.isNTLM();
        isDNSProxy = this.profile.isDNSProxy();
        isPAC = this.profile.isPAC();
        if (isAuth) {
            auth = "true";
            user = this.profile.getUser();
            password = this.profile.getPassword();
        } else {
            auth = "false";
            user = "";
            password = "";
        }
        if (isNTLM)
            domain = this.profile.getDomain();
        else
            domain = "";

        hasRedirectSupport = Utils.getHasRedirectSupport();         //是否可以设置转发，有些手机不行
        handleCommand();
    }

    private String getUid(String packageNmae) {
        String ret = "";
        String cmds = "chmod 777 /data/system/packages.list";
        ShellUtils.execCommand(cmds, true);
        cmds = "cat /data/system/packages.list |grep " + packageNmae;
        ShellUtils.CommandResult cret = ShellUtils.execCommand(cmds, true);
        String[] cretArray = cret.successMsg.split(" ");
        ret = cretArray[1];
        return ret;
    }

}
